#include "GLPrimitiveRenderer.h"
#include "GLTexture.h"
#include "..\RendererBase.h"
#include "GLRenderList.h"

#include <gl\glew.h>

extern void InitExtensions();
extern bool _supportVA, _supportVBO, _supportShaders;
extern GLuint _attribVertexPosition, _attribVertexColor, _attribVertexTexCoord;
extern GLuint _uniformMVP, _uniformBoundTexture;

RenderListBase *GLPrimitiveRenderer::CreateRenderList()
{
 	return new GLRenderList();
}

GLPrimitiveRenderer::GLPrimitiveRenderer(RendererBase *r) :
	PrimitiveRendererBase(r)
{
}

GLPrimitiveRenderer::~GLPrimitiveRenderer()
{
	wxMutexLocker lock(_lock);

	for (size_t i = 0; i < _renderLists.size(); ++i)
		delete _renderLists[i];
}

/// <summary>
/// Render this chunk render data.
/// </summary>
void GLPrimitiveRenderer::Render()
{
	auto err = _lock.TryLock();

	if (err == wxMUTEX_NO_ERROR)
	{
		if (_supportVBO && !_vboCompiled)
		{
			for (auto it = _renderLists.begin(); it != _renderLists.end(); ++it)
			{
				auto list = static_cast<GLRenderList*>(*it);

				if (list->VBO == 0)
					glGenBuffers(1, &list->VBO);

				glBindBuffer(GL_ARRAY_BUFFER, list->VBO);
				glBufferData(GL_ARRAY_BUFFER, list->CompiledSize * sizeof(RenderVertex), list->CompiledVertices.data(), GL_STATIC_DRAW);
				
				std::vector<RenderVertex>().swap(list->CompiledVertices);

				glBindBuffer(GL_ARRAY_BUFFER, 0);
			}

			_vboCompiled = true;
		}

		if (_supportVA)
			DrawArrays();
		else
			DrawImmediate();

		_lock.Unlock();
	}
}

void GLPrimitiveRenderer::Compile(const bool &allowCacheLoading)
{
	wxMutexLocker lock(_lock);

	_vboCompiled = false;
	PrimitiveRendererBase::Compile(allowCacheLoading);
}

void GLPrimitiveRenderer::DrawImmediate()
{
	/*glBindTexture(GL_TEXTURE_2D, 0);

	for (auto it = _renderLists.begin(); it != _renderLists.end(); ++it)
	{
		auto l = (*it);
		
		if (l->Texture != nullptr)
			glBindTexture(GL_TEXTURE_2D, l->Texture->GetID());

		glBegin(GL_QUADS);

		float	*vpp = &l->Vertices[0].X,
				*tpp = &l->TexCoords[0].X;
		byte	*cpp = l->Colors.data();
		
		for (size_t i = 0; i < l->Vertices.size() / 4; ++i)
		{
			for (int x = 0; x < 4; ++x)
			{
				glColor4ubv(cpp);
				glTexCoord2fv(tpp);
				glVertex3fv(vpp);

				cpp += 4;
				tpp += 2;
				vpp += 3;
			}
		}

		glEnd();
	}

	glBindTexture(GL_TEXTURE_2D, 0);*/
}

void GLPrimitiveRenderer::DrawArrays()
{
	glBindTexture(GL_TEXTURE_2D, 0);

	if (_supportShaders)
	{
		glEnableVertexAttribArray(_attribVertexPosition);
		glEnableVertexAttribArray(_attribVertexTexCoord);
		glEnableVertexAttribArray(_attribVertexColor);
	}
	else
	{
		glEnableClientState(GL_VERTEX_ARRAY);
		glEnableClientState(GL_COLOR_ARRAY);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	for (auto it = _renderLists.begin(); it != _renderLists.end(); ++it)
	{
		auto l = (*it);

		if (l->Texture != nullptr)
		{
			if (_supportShaders)
				glActiveTexture(GL_TEXTURE0);
			
			glBindTexture(GL_TEXTURE_2D, static_cast<GLTexture*>(l->Texture)->GetID());
		
			if (_supportShaders)
				glUniform1i(_uniformBoundTexture, 0);
		}

		RenderVertex *begin = l->CompiledVertices.data();
		const size_t stride = sizeof(RenderVertex);

		if (_supportVBO)
		{
			glBindBuffer(GL_ARRAY_BUFFER, static_cast<GLRenderList*>(l)->VBO);
			
			auto ptOffs = reinterpret_cast<byte*>(offsetof(RenderVertex, Point)),
				tcOffs = reinterpret_cast<byte*>(offsetof(RenderVertex, TexCoord)),
				cOffs = reinterpret_cast<byte*>(offsetof(RenderVertex, Color));

			if (_supportShaders)
			{
				glVertexAttribPointer(_attribVertexPosition, 3, GL_FLOAT, false, sizeof(RenderVertex), ptOffs);
				glVertexAttribPointer(_attribVertexTexCoord, 2, GL_FLOAT, false, sizeof(RenderVertex), tcOffs);
				glVertexAttribPointer(_attribVertexColor, 4, GL_FLOAT, false, sizeof(RenderVertex), cOffs);
			}
			else
			{
				glVertexPointer(3, GL_FLOAT, stride, ptOffs);
				glTexCoordPointer(2, GL_FLOAT, stride, tcOffs);
				glColorPointer(4, GL_FLOAT, stride, cOffs);
			}
		}
		else
		{
			glVertexPointer(3, GL_FLOAT, stride, &begin->Point.X);
			glTexCoordPointer(2, GL_FLOAT, stride, &begin->TexCoord.X);
			glColorPointer(4, GL_FLOAT, stride, &begin->Color[0]);
		}
		
		glDrawArrays(GL_QUADS, 0, l->CompiledSize);
	}

	if (_supportVBO)
		glBindBuffer(GL_ARRAY_BUFFER, 0);

	if (_supportShaders)
	{
		glDisableVertexAttribArray(_attribVertexPosition);
		glDisableVertexAttribArray(_attribVertexTexCoord);
		glDisableVertexAttribArray(_attribVertexColor);
	}
	else
	{
		glDisableClientState(GL_VERTEX_ARRAY);
		glDisableClientState(GL_COLOR_ARRAY);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	glBindTexture(GL_TEXTURE_2D, 0);
}